
/**
 * Baijiahulian.com Inc. Copyright (c) 2014-2016 All Rights Reserved.
 */

package cn.wenhao.javaClassReload.javaClassElements;

import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.objectweb.asm.Type;

import cn.wenhao.javaClassReload.JavaClassModify.classTransformer.utils.ByteSourceClassLoader;
import cn.wenhao.javaClassReload.invoker.MethodInvoker;
import cn.wenhao.javaClassReload.utils.NameRegister;
import cn.wenhao.javaClassReload.zTestCode.afterChange.App;
import lombok.extern.slf4j.Slf4j;

/**
 * @say little Boy, don't be sad.
 * @name Rezar
 * @time Oct 27, 2016
 * @Desc 用于替代原有字节码对象执行方法的实际调用.
 */
@Slf4j
public class ObjExecutor implements NameRegister {

    public String hoastClass;

    private Object host;

    private boolean hasInit;

    /**
     * 对所有公有方法的访问,针对私有方法或者保护方法<br/>
     * @1:在转换的时候全部变成公有的.<br/>
     * @2:访问的字节码使用invokespecial指令.
     */
    private Map<Integer, MethodInvoker> methodInvokers = new HashMap<>();

    /**
     * 静态方法的访问
     */
    private Map<Integer, MethodInvoker> staticMethodInvokers = new HashMap<>();

    public ObjExecutor() {

    }

    public Object invokeMethod(Integer methodFlag, Object host, Object...argus) {
        MethodInvoker invoker = this.methodInvokers.get(methodFlag);
        if (invoker == null) {
            throw new IllegalArgumentException(String.format("no such method %s in class:%s ", methodFlag, hoastClass));
        }
        log.debug("invoke is : {} and params's size is :{}", invoker.getClass().getName(),
            argus == null ? "EMPTY" : argus.length);
        return invoker.invoke(host, argus);
    }

    public Object invokeStaticMethod(Integer methodFlag, Object...argus) {
        MethodInvoker invoker = this.staticMethodInvokers.get(methodFlag);
        if (invoker == null) {
            throw new IllegalArgumentException(
                String.format("no such static method %s in class:%s", methodFlag, hoastClass));
        }
        return invoker.invoke(invoker, argus);
    }

    /**
     * 添加一个方法的调用器
     * 
     * @param methodFlag
     * @param methodInvoker
     */
    public void addMethodInvoker(Integer methodFlag, MethodInvoker methodInvoker) {
        log.debug("flag : {} for invoker : {} ", methodFlag, methodInvoker.getClass().getName());
        this.methodInvokers.put(methodFlag, methodInvoker);
    }

    /**
     * 添加一个静态方法的调用器
     * 
     * @param methodFlag
     * @param methodInvoker
     */
    public void addStaticInvoker(Integer methodFlag, MethodInvoker methodInvoker) {
        this.staticMethodInvokers.put(methodFlag, methodInvoker);
    }

    private AtomicInteger idIncrer = new AtomicInteger(0);
    private Map<Object, Integer> flagMapper = new HashMap<>();

    /**
     * 允许传入属性的名称,方法名称+描述
     */
    @Override
    public Integer register(Object registerObj) {
        Integer flag = flagMapper.get(registerObj);
        if (flag == null) {
            synchronized (registerObj.toString().intern()) {
                if (flagMapper.get(registerObj) == null) {
                    flag = idIncrer.incrementAndGet();
                    flagMapper.put(registerObj, flag);
                }
            }
        }
        log.debug("make flag : {} for registerObj:{} ", flag, registerObj);
        return flag;
    }

    /**
     * @param host
     */
    public void initMethodInvoker(Object host) {
        if (hasInit) {
            return;
        }
        this.host = host;
        Collection<MethodInvoker> values = this.methodInvokers.values();
        for (MethodInvoker mi : values) {
            mi.setHost(host);
        }
        hasInit = true;
    }

    /**
     * @param typeDescriptor : eg:Lcn/wenhao/javaClassReload/zTestCode/afterChange/App;
     * @param newMethodInvokerBytes
     */
    public void replaceMethodInvoker(String newClassName, String methodNameAndDesc, byte[] newMethodInvokerBytes) {
        MethodInvoker newMi = null;
        try {
            newMi = (MethodInvoker) ByteSourceClassLoader
                .loadClass(this.getClass().getClassLoader(), newClassName, newMethodInvokerBytes).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        if (newMi == null) {
            log.info("newMi is null ");
            return;
        }
        newMi.setHost(this.host);
        Integer register = this.register(methodNameAndDesc);
        this.methodInvokers.put(register, newMi);
    }

    public static void main(String[] args) {
        System.out.println(Type.getType(App.class).getDescriptor());
    }

    public String toString() {
        return this.hoastClass + ":" + this.methodInvokers.values().size();
    }

}
